
import config
import entity
import relationship
import attribute
import path
import rectangles

class Drawing:
    def __init__(self, canvas, cfg=None):
        if cfg is None:
            cfg = config.Configuration()
        self.cfg = cfg
        self.canvas = canvas
        self.nameToEntity = {}
        self.nameToRelationship = {}
        self.namesToAttributeAndPath = {}
        self.nameRoleToPath = {}
    def dumpList(self):
        "dump data as list format"
        # I don't know why the x values need to be adjusted funnily, it probably is a bug that needs fixing elsewhere... xxx
        result = []
        n2r = self.nameToRelationship
        n2e = self.nameToEntity
        n2a = self.namesToAttributeAndPath
        rnames = n2r.keys()
        rnames.sort()
        enames = n2e.keys()
        enames.sort()
        attNamesAndEntities = n2a.keys()
        attNamesAndEntities.sort()
        for rname in rnames:
            rel = n2r[rname]
            rect = rel.rectangle()
            weak = rel.weak
            result.append( ("relationship", rname, rect.xmin, rect.ymin+rect.height, weak) )
        for ename in enames:
            ent = n2e[ename]
            rect = ent.rectangle()
            weak = ent.weak
            result.append( ("entity", ename, rect.xmin, rect.ymin+rect.height, weak) )
        for attNameAndEntity in attNamesAndEntities:
            (aName, eName) = attNameAndEntity
            (attr, path) = n2a[attNameAndEntity]
            rect = attr.rectangle()
            print "dumping attr", attr.name, id(attr), attr.p, rect
            iskey = attr.underline
            print "rect.xmin, rect.ymin, rect.width, rect.height", rect.xmin, rect.ymin, rect.width, rect.height
            print ("attribute", aName, eName, rect.xmin, rect.ymin+rect.height, iskey)
            result.append(("attribute", aName, eName, rect.xmin, rect.ymin+rect.height, iskey))
        for nameRole in self.nameRoleToPath:
            (relname, rolename, entName) = nameRole
            (path, minimum, maximum) = self.nameRoleToPath[nameRole]
            result.append( ("participate", entName, relname, rolename,  minimum, maximum) )
        return result
    def adjust(self):
        import rectangles
        (adjacencies, nameToRectangles) = self.getAdjacencyListAndNameToRectangle()
        rectangles.centerRectangles(nameToRectangles.values(), 0, 0)
        # divide the rectangles into attributes and non-attributes
        attributeNames = self.namesToAttributeAndPath.keys()
        attributeRectangles = {}
        otherRectangles = {}
        for name in nameToRectangles:
            if name in self.namesToAttributeAndPath:
                attributeRectangles[name] = nameToRectangles[name]
            else:
                otherRectangles[name] = nameToRectangles[name]        
        # first layout the other rectangles with a large border
        """
        #pr "border 100"
        otherRectangles = rectangles.layoutRectangles(otherRectangles,
                                                          adjacencies,
                                                          initOffset=800,
                                                          attractionFactor=0.0001,
                                                          repulsionFactor=100000,
                                                          reduction=0.7,
                                                          offsetLimit=210,
                                                          borderSize=100,
                                                          debug=False)
                                                          """
        #pr "border 35"
        otherRectangles = rectangles.layoutRectangles(otherRectangles,
                                                          adjacencies,
                                                          initOffset=400,
                                                          attractionFactor=10,
                                                          repulsionFactor=1000,
                                                          reduction=0.9,
                                                          offsetLimit=110,
                                                          borderSize=35,
                                                          debug=False,
                                                      radius=300)
        nameToRectangles.update(otherRectangles)
        # then layout the attributes with a small border and others fixed
        #pr "attributes"
        attributeRectangles = rectangles.layoutRectangles(attributeRectangles,
                                                          adjacencies,
                                                          initOffset=2000,
                                                          repulsionFactor=10000,
                                                          attractionFactor=10000,
                                                          reduction=0.5,
                                                          borderSize=10,
                                                          debug=False,
                                                          radius=300,
                                                          fixedIdToRectangle=otherRectangles)
        newNameToRectangles = otherRectangles
        newNameToRectangles.update(attributeRectangles)
        self.adjustRectangles(newNameToRectangles)
    def getAdjacencyListAndNameToRectangle(self):
        adjacencies = []
        nameToRectangle = {}
        for (rname, role, ename) in self.nameRoleToPath:
            adjacencies.append( (rname, ename) )
        for nameAndEntityName in self.namesToAttributeAndPath:
            (name, ename) = nameAndEntityName
            adjacencies.append( (nameAndEntityName, ename) )
            (attribute, path) = self.namesToAttributeAndPath[nameAndEntityName]
            nameToRectangle[nameAndEntityName] = attribute.rectangle()
        for name in self.nameToEntity:
            nameToRectangle[name] = self.nameToEntity[name].rectangle()
        for name in self.nameToRelationship:
            nameToRectangle[name] = self.nameToRelationship[name].rectangle()
        return (adjacencies, nameToRectangle)
    def adjustRectangles(self, nameToRectangle, cx=300, cy=400):
        rectangles.centerRectangles(nameToRectangle.values(), cx, cy)
        for nameAndEntityName in self.namesToAttributeAndPath:
            (attribute, path) = self.namesToAttributeAndPath[nameAndEntityName]
            rectangle = nameToRectangle[nameAndEntityName]
            attribute.setRectangle(rectangle)
        for name in self.nameToRelationship:
            rel = self.nameToRelationship[name]
            rectangle = nameToRectangle[name]
            rel.setRectangle(rectangle)
        for name in self.nameToEntity:
            ent = self.nameToEntity[name]
            rectangle = nameToRectangle[name]
            ent.setRectangle(rectangle)
    def addEntity(self, name, x, y, weak=False):
        theEntity = entity.Entity(name, self.canvas, x, y, cfg=self.cfg, weak=weak)
        self.nameToEntity[name] = theEntity
    def addRelationship(self, name, x, y, weak=False):
        #pr "addRelationship", (name, x, y)
        theRelationship = relationship.Relationship(name, self.canvas, x, y, cfg=self.cfg, weak=weak)
        self.nameToRelationship[name] = theRelationship
        #pr "added relationship", self.nameToRelationship.keys()
    def participate(self, entityName, relationshipName, roleName, minimum, maximum):
        #pr "participate", (self, entityName, relationshipName, roleName, minimum, maximum)
        theEntity = self.nameToEntity[entityName]
        #pr "relationships", self.nameToRelationship.keys()
        theRelationship = self.nameToRelationship[relationshipName]
        #origins = theEntity.destinationMap()
        #destinations = theRelationship.destinationMap()
        #barriers = self.allBarriers(origins, destinations)
        #(start, end, points) = path.getPath(origins, destinations, barriers, self.cfg.delta)
        #thePath = path.CardinalPath(start, points, end, minimum, maximum, self.canvas, origins, destinations)
        thePath = None
        # find an unused rolName
        rolecount = 0
        while self.nameRoleToPath.get( (relationshipName, roleName, entityName) ) is not None:
            rolecount+=1
            roleName = str(roleName)+str(rolecount)
        self.nameRoleToPath[ (relationshipName, roleName, entityName) ] = (thePath, minimum, maximum)
    def addAttribute(self, name, entityName, x, y, iskey=False):
        theAttribute = attribute.Attribute(name, self.canvas, x,y, cfg=self.cfg, underline=iskey)
        theEntity = self.nameToEntity.get(entityName)
        if theEntity is None:
            theEntity = self.nameToRelationship.get(entityName)
        #origins = theAttribute.destinationMap()
        #destinations = theEntity.destinationMap()
        #barriers = self.allBarriers(origins, destinations)
        #(start, end, points) = path.getPath(origins, destinations, barriers, self.cfg.delta)
        #thePath = path.CardinalPath(start, points, end, None, None, self.canvas, origins, destinations)
        thePath = None
        self.namesToAttributeAndPath[(name, entityName)] = (theAttribute, thePath)
    def prepare(self):
        "assign all paths, avoiding obstacles"
        participations = self.nameRoleToPath.keys()
        participations.sort()
        for p in participations:
            (rname, rolename, ename) = p
            (thePath, minimum, maximum) = self.nameRoleToPath[p]
            theEntity = self.nameToEntity[ename]
            theRelationship = self.nameToRelationship[rname]
            origins = theEntity.destinationMap()
            destinations = theRelationship.destinationMap()
            barriers = self.allBarriers(origins, destinations)
            (start, end, points) = path.getPath(origins, destinations, barriers, self.cfg.delta)
            thePath = path.CardinalPath(start, points, end, minimum, maximum, self.canvas, origins, destinations)
            self.nameRoleToPath[ (rname, rolename, ename) ] = (thePath, minimum, maximum)
        attributePairs = self.namesToAttributeAndPath.keys()
        attributePairs.sort()
        for p in attributePairs:
            (name, entityName) = p
            (theAttribute, thePath) = self.namesToAttributeAndPath[p]
            theEntity = self.nameToEntity.get(entityName)
            if theEntity is None:
                theEntity = self.nameToRelationship.get(entityName)
            origins = theAttribute.destinationMap()
            destinations = theEntity.destinationMap()
            barriers = self.allBarriers(origins, destinations)
            (start, end, points) = path.getPath(origins, destinations, barriers, self.cfg.delta)
            thePath = path.CardinalPath(start, points, end, None, None, self.canvas, origins, destinations)
            self.namesToAttributeAndPath[(name, entityName)] = (theAttribute, thePath)
    def draw(self):
        self.canvas.setBackgroundCallback("ERdiagram")
        for e in self.nameToEntity.values():
            e.draw()
        for r in self.nameToRelationship.values():
            r.draw()
        for (a, p) in self.namesToAttributeAndPath.values():
            a.draw()
        for (p,minimum,maximum) in self.nameRoleToPath.values():
            p.draw()
        for (a, p) in self.namesToAttributeAndPath.values():
            p.draw()
    def allBarriers(self, origins, destinations):
        barriers = {}
        for e in self.nameToEntity.values():
            barriers.update(e.barriers())
        for r in self.nameToRelationship.values():
            barriers.update(r.barriers())
        for (a, p) in self.namesToAttributeAndPath.values():
            barriers.update(a.barriers())
        for p in origins.keys()+destinations.keys():
            if barriers.has_key(p):
                del barriers[p]
        for (a, p) in self.namesToAttributeAndPath.values():
            if p is not None:
                barriers.update(p.barriers())
        for (p, minimum, maximum) in self.nameRoleToPath.values():
            if p is not None:
                barriers.update(p.barriers())
        return barriers
    
def test(fontdir=".", outfile="/tmp/out.png"):
    from skimpyGimpy import canvas
    from entity import Entity
    c = canvas.Canvas()
    #c.addFont("propell", fontdir+"/5x7.bdf")
    c.addFont("propell", fontdir+"/propell.bdf")
    d = Drawing(c)
    d.addEntity("EMPLOYEE", 0, 0)
    d.addEntity("DEPT", 0, 200)
    d.addEntity("PROJECT", -200, 0)
    d.addRelationship(("WORKS", "FOR"), 0, 100)
    d.addRelationship(("MANAGES"), -100, 0)
    d.addRelationship("WORKS-ON", -200,100)
    d.addRelationship("OWNS", 200, 100)
    d.participate("DEPT", "OWNS", "dept", 1, "many")
    d.participate("PROJECT", "OWNS", "p", 1, "many")
    d.participate("EMPLOYEE", ("WORKS", "FOR"), "EMP", 1, 1)
    d.participate("DEPT", ("WORKS", "FOR"), "dept", 0, "many")
    d.participate("EMPLOYEE", "MANAGES", "EMP", 0, 1)
    d.participate("DEPT", "MANAGES", "dept", 1, 1)
    d.participate("EMPLOYEE", "WORKS-ON", "emp", 0, "many")
    d.participate("PROJECT", "WORKS-ON", "proj", 0, "many")
    d.addAttribute("ssn", "EMPLOYEE", 300, -100, iskey=True)
    d.addAttribute("enum", "EMPLOYEE", 0, -100)
    d.addAttribute("first", "EMPLOYEE", 100, -100)
    d.addAttribute("last", "EMPLOYEE", 200, -100)
    d.addAttribute("dnum", "DEPT", -100, 200)
    d.prepare()
    d.draw()
    c.dumpToPNG(outfile)
    print "test output to", outfile
   
def test2(fontdir=".", outfile="/tmp/out2.png"):
    from skimpyGimpy import canvas
    from entity import Entity
    c = canvas.Canvas()
    #c.addFont("propell", fontdir+"/5x7.bdf")
    c.addFont("propell", fontdir+"/propell.bdf")
    d = Drawing(c)
    d.addEntity("EMPLOYEE", 0, 0)
    d.addEntity("DEPT", 0, 100)
    d.addEntity("PROJECT", 0, 200)
    d.addRelationship(("WORKS", "FOR"), -100, 0)
    d.addRelationship(("MANAGES"), -100, 100)
    d.addRelationship("WORKS-ON", -100, 200)
    d.addRelationship("OWNS", -100, 300)
    d.participate("DEPT", "OWNS", "dept", 1, "many")
    d.participate("PROJECT", "OWNS", "p", 1, "many")
    d.participate("EMPLOYEE", ("WORKS", "FOR"), "EMP", 1, 1)
    d.participate("DEPT", ("WORKS", "FOR"), "dept", 0, "many")
    d.participate("EMPLOYEE", "MANAGES", "EMP", 0, 1)
    d.participate("DEPT", "MANAGES", "dept", 1, 1)
    d.participate("EMPLOYEE", "WORKS-ON", "emp", 0, "many")
    d.participate("PROJECT", "WORKS-ON", "proj", 0, "many")
    d.addAttribute("ssn", "EMPLOYEE", 100, 300, iskey=True)
    d.addAttribute("enum", "EMPLOYEE", 100,150)
    d.addAttribute("first", "EMPLOYEE", 100, 50)
    d.addAttribute("last", "EMPLOYEE", 100, 200)
    d.addAttribute("dnum", "DEPT", 100, 0, iskey=True)
    d.addAttribute("title", ("WORKS", "FOR"), 100, -50)
    d.addAttribute("since", "OWNS", 100, -100)
    d.prepare()
    d.draw()
    c.dumpToPNG(outfile)
    print "test output to", outfile
    
def test3(fontdir=".", outfile="/tmp/out3.png", adjust=False):
    from skimpyGimpy import canvas
    from entity import Entity
    c = canvas.Canvas()
    c.addFont("propell", fontdir+"/propell.bdf")
    d = Drawing(c)
    d.addEntity("EMPLOYEE_INFO", 0, 0)
    d.addEntity("DEPT", 800, 0)
    d.addEntity("PROJECT", 400, 0)
    d.addRelationship(("WORKS", "FOR"), 100, 100)
    d.addRelationship(("MANAGES"), 100, -50)
    d.addRelationship("WORKS-ON", 300, -100)
    d.addRelationship("OWNS", 600, 0)
    d.addAttribute("ssn", "EMPLOYEE_INFO", 0, -50, iskey=True)
    d.participate("DEPT", "OWNS", "dept", 1, "many")
    d.participate("PROJECT", "OWNS", "p", 1, "many")
    d.participate("EMPLOYEE_INFO", ("WORKS", "FOR"), "EMP", 1, 1)
    d.participate("DEPT", ("WORKS", "FOR"), "dept", 0, "many")
    d.participate("EMPLOYEE_INFO", "MANAGES", "EMP", 0, 1)
    d.participate("DEPT", "MANAGES", "dept", 1, 1)
    d.participate("EMPLOYEE_INFO", "WORKS-ON", "emp", 0, "many")
    d.participate("PROJECT", "WORKS-ON", "proj", 0, "many")
    d.addAttribute("enum", "EMPLOYEE_INFO", 0,-100)
    d.addAttribute("first", "EMPLOYEE_INFO", 0,50)
    d.addAttribute("last", "EMPLOYEE_INFO", 0,100)
    d.addAttribute(("dept", "num"), "DEPT", 200, -50, iskey=True)
    d.addAttribute("title", ("WORKS", "FOR"), 100, 150)
    d.addAttribute("since", "OWNS", 300, 60)
    if adjust:
        print "now adjusting"
        d.adjust()
    d.prepare()
    d.draw()
    c.dumpToPNG(outfile)
    print "test output to", outfile

if __name__=="__main__":
    test3()
    test3(outfile="/tmp/adjusted.png", adjust=True)

    
